package sensimulator.intersection.traffic;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.ImageObserver;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BlockingQueue;

import sennetworkserver.LaneStore;
import sennetworkserver.TrafficLight;
import sensimulator.intersection.Drawable;
import sensimulator.intersection.Painter;
import sensimulator.intersection.lanes.Lane;
import sensimulator.intersection.lanes.LaneNode;

public abstract class TrafficUser implements Drawable {
	
	static final float ROTATION_STEP = 0.001f;

	static final float MIN_FOLLOW_DISTANCE = 5f;
	
	static final int HITBOX_PADDING = 5;
	
	protected Lane lane;
	
	protected Point2D position;
	
	protected float speed;
	
	protected float maxSpeed;

	protected LaneNode currentNode;
	
	protected LaneNode nextNode;
	
	protected long nodeStartTime;
	
	protected boolean disposed;
	
	protected Image image;
	
	protected double targetRotation;
	
	protected double rotation;
	
	public TrafficUser(Lane lane, Point2D position,  float maxSpeed) {
		this.lane = lane;
		this.position = position;
		this.maxSpeed = maxSpeed;
		
		this.currentNode = lane.getFirstNode();
		this.nextNode = currentNode.getNext();
		this.nodeStartTime = 0;
		rotation = targetRotation = Math.atan2(nextNode.getPoint().y - currentNode.getPoint().y, 
 	          								   nextNode.getPoint().x - currentNode.getPoint().x);
	}

	public void tick(long time, int delta) {
		if (nextNode == null || disposed)
			return;

		// Check if we have to wait for the light
		if (currentNode.isTrafficLight()) {
			sennetworkserver.Lane networkLane = LaneStore.getLane(lane.getId());
			
			if (networkLane.getTrafficLight().getState() != TrafficLight.GREEN) {
				speed = 0f;
				return;
			}
		}
		
		moveAndRotate(delta);
	}
	
	protected void moveAndRotate(int delta) {

		if (isTailgating()) {
			speed = 0f;
			return;
		}
		
		// Calculate node delta
		Point2D deltaPos = new Point2D.Double(nextNode.getPoint().getX() - position.getX(),
										      nextNode.getPoint().getY() - position.getY());
		double length = deltaPos.distance(0, 0);
		
		speed = maxSpeed;
		
		// If the target is close enough to get in 1 tick
		if (length < speed * delta) {
			position = nextNode.getPoint();

			// We passed a traffic light, trigger 1M sensor
			if (currentNode.isTrafficLight())
				LaneStore.getLane(lane.getId()).getSensor1m().notifyObservers();
			
			currentNode = nextNode;
			nextNode = currentNode.getNext();
			
			// End reached, dispose object
			if (nextNode == null) {
				disposed = true;
				
				if (lane.getFollowUp() != null)
					lane.getFollowUp().spawnTraffic(true);
			}
			
		} else {
			// Calculate step length
			Point2D step = new Point2D.Double(deltaPos.getX() / length, deltaPos.getY() / length);
			position = new Point2D.Double(position.getX() + step.getX() * speed * delta, 
								 		  position.getY() + step.getY() * speed * delta);
		}

		if (nextNode != null)
			targetRotation = Math.atan2(nextNode.getPoint().y - currentNode.getPoint().y, 
					 	          		nextNode.getPoint().x - currentNode.getPoint().x);
	
		// Check how much we need to rotate
		double rotationDelta = rotation - targetRotation;
		if (rotationDelta > Math.PI)
			rotation -= Math.PI * 2;
		else if (rotationDelta < -Math.PI)
			rotation += Math.PI * 2;
		
		// Rotate towards target rotation 
		if (rotation > targetRotation + ROTATION_STEP * delta)
			rotation -= ROTATION_STEP * delta;
		else if (rotation < targetRotation - ROTATION_STEP * delta)
			rotation += ROTATION_STEP * delta;
		else
			rotation = targetRotation;
	}
	
	protected boolean isTailgating() {
		// Calculate distance to next user
		List<TrafficUser> traffic = lane.getTraffic();
		int index = traffic.indexOf(this);		
		
		double distanceToNext = Float.MAX_VALUE;
		double followDistance = MIN_FOLLOW_DISTANCE;
		
		// If not first user in row
		if (index > 0) {
			TrafficUser nextUser = traffic.get(index - 1);
			distanceToNext = position.distance(nextUser.position);
			followDistance += nextUser.getWidth();
		}
		
		// If we are within our minimum follow distance, we are tailgating
		return distanceToNext < followDistance;
	}

	public void paint(Graphics g, ImageObserver imageObserver) {
		if (image == null)
			return;
			
		Graphics2D g2 = (Graphics2D) g;
		
		AffineTransform t = new AffineTransform();
		t.translate((int) (position.getX()), 
				    (int) (position.getY()));
		t.rotate(rotation);
		
		g2.setTransform(t);	
		g2.drawImage(image, -getWidth(), -getHeight() / 2, imageObserver);
		
		g2.setTransform(new AffineTransform());
	}
	
	public void dispose() {
		disposed = true;
	}
	
	public boolean collidesWith(TrafficUser other) {
		Area a = getArea();
		a.intersect(other.getArea());
		
		return !a.isEmpty();
	}
	
	public Lane getLane() {
		return lane;
	}
	
	public Area getArea() {
		Rectangle rect = new Rectangle(-getWidth() + HITBOX_PADDING, 
									   -getHeight() / 2 + HITBOX_PADDING, 
									   getWidth() - HITBOX_PADDING * 2, 
									   getHeight() - HITBOX_PADDING * 2);
		
		AffineTransform t = new AffineTransform();
		t.translate((int) (position.getX()), 
			        (int) (position.getY()));
		t.rotate(rotation);
		
		return new Area(t.createTransformedShape(rect));
	}

	public int getWidth() {
		return image.getWidth(null);
	}
	
	public int getHeight() {
		return image.getHeight(null);
	}

	public Point2D getPosition() {
		return position;
	}
	
	public float getSpeed() {
		return speed;
	}
	
	public float getMaxSpeed() {
		return maxSpeed;
	}


	public boolean isDisposed() {
		return disposed;
	}
	
	public int getZOrder() {
		return Painter.TRAFFIC_LAYER;
	}
	
}
